vue使用socketIoClient连接socketIo服务端反复重连问题

您所在的位置:网站首页 vue websocket 断线重连 vue使用socketIoClient连接socketIo服务端反复重连问题

vue使用socketIoClient连接socketIo服务端反复重连问题

2023-09-27 19:23| 来源: 网络整理| 查看: 265

最近在项目中使用到了socketIo,spring boot集成socketIo作为服务端,需要前端页面vue使用socketIoClient连接服务端并监听消息,结果在连接socketIo服务端的时候出现了反复连接的情况,当时这个问题卡住了一天时间,网上面关于这个的问题特别少,就问题描述及解决过程记录如下,以供参考。

简单介绍spring boot后端集成socketIo步骤: 添加依赖:

com.corundumstudio.socketio netty-socketio 1.7.17

这儿使用的是netty-socketIo,netty-socketio是一个开源的Socket.io服务器端的一个java的实现,它基于Netty框架,可用于服务端推送消息给客户端。

application配置参数:

# SocketIO配置 socketIo: host: 0.0.0.0 # SocketIO端口 port: 8083 # 连接数大小 workCount: 100 # 允许客户请求 allowCustomRequests: true # 协议升级超时时间(毫秒),默认10秒,HTTP握手升级为ws协议超时时间 upgradeTimeout: 10000 # Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件 pingTimeout: 60000 # Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔 pingInterval: 25000 # 设置HTTP交互最大内容长度 maxHttpContentLength: 1048576 # 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器 maxFramePayloadLength: 1048576

config配置代码:

/**logger*/ private static final Logger logger = LoggerFactory.getLogger(SocketConfig.class); @Value("${socketIo.host}") private String host; @Value("${socketIo.port}") private Integer port; @Value("${socketIo.workCount}") private int workCount; @Value("${socketIo.allowCustomRequests}") private boolean allowCustomRequests; @Value("${socketIo.upgradeTimeout}") private int upgradeTimeout; @Value("${socketIo.pingTimeout}") private int pingTimeout; @Value("${socketIo.pingInterval}") private int pingInterval; @Value("${socketIo.maxFramePayloadLength}") private int maxFramePayloadLength; @Value("${socketIo.maxHttpContentLength}") private int maxHttpContentLength; /** * SocketIOServer配置 */ @Bean("socketIOServer") public SocketIOServer socketIOServer() { com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); //配置host // config.setHostname(host); //配置端口 config.setPort(port); //开启Socket端口复用 com.corundumstudio.socketio.SocketConfig socketConfig = new com.corundumstudio.socketio.SocketConfig(); socketConfig.setReuseAddress(true); config.setSocketConfig(socketConfig); //连接数大小 config.setWorkerThreads(workCount); //允许客户请求 config.setAllowCustomRequests(allowCustomRequests); //协议升级超时时间(毫秒),默认10秒,HTTP握手升级为ws协议超时时间 config.setUpgradeTimeout(upgradeTimeout); //Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件 config.setPingTimeout(pingTimeout); //Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔 config.setPingInterval(pingInterval); //设置HTTP交互最大内容长度 config.setMaxHttpContentLength(maxHttpContentLength); //设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器 config.setMaxFramePayloadLength(maxFramePayloadLength); config.setTransports(Transport.POLLING, Transport.WEBSOCKET); /*config.setOrigin("http://localhost:3000");*/ return new SocketIOServer(config); } /** * 开启SocketIOServer注解支持 */ @Bean public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) { return new SpringAnnotationScanner(socketServer); }

然后是监听代码:

@Component public class SocketHandler { /**logger*/ private Logger logger = LoggerFactory.getLogger(SocketHandler.class); /**存已连接的客户端*/ private Map clientMap = new ConcurrentHashMap(16); private final SocketIOServer socketIOServer; @Autowired private RedisUtil redisUtil; @Autowired public SocketHandler(SocketIOServer socketIOServer) { this.socketIOServer = socketIOServer; } /** * 当客户端发起连接时调用 * @param socketIOClient 客户端 */ @OnConnect public void onConnect(SocketIOClient socketIOClient) { //获取socketClient连接参数 String userName = socketIOClient.getHandshakeData().getSingleUrlParam(EwsCommonConstants.SystemParam.SOCKET_USER_NAME); String appKey = socketIOClient.getHandshakeData().getSingleUrlParam(EwsCommonConstants.SystemParam.APP_KEY); String roomId = socketIOClient.getHandshakeData().getSingleUrlParam(EwsCommonConstants.SystemParam.SOCKET_ROOM_ID); Map headers = new HashMap(); for (Map.Entry entry : socketIOClient.getHandshakeData().getHttpHeaders().entries()){ headers.put(entry.getKey(), entry.getValue()); } logger.info("header:"+ JSON.toJSONString(headers)); //clientMap存放连接客户端信息 if (StringUtils.isNotBlank(roomId)) { logger.info("用户{}开启长连接通知, roomId: {}, NettySocketSessionId: {}, NettySocketRemoteAddress: {}", userName, roomId, socketIOClient.getSessionId().toString(), socketIOClient.getRemoteAddress().toString()); List uuidList = new ArrayList(); //clientMap-key为appKey与room编号组合 String clientKey = StringUtils.concatStr(appKey, EwsCommonConstants.SystemParam.CON_SIGN, roomId); if(CollectionUtils.isNotEmpty(clientMap.get(clientKey))){ uuidList = clientMap.get(clientKey); } uuidList.add(socketIOClient.getSessionId()); clientMap.put(clientKey, uuidList); logger.info(JSON.toJSONString(clientMap)); //加入房间 socketIOClient.joinRoom(clientKey); } } /** * 客户端断开连接时调用,刷新客户端信息 * @param socketIOClient 客户端 */ @OnDisconnect public void onDisConnect(SocketIOClient socketIOClient) { String userName = socketIOClient.getHandshakeData().getSingleUrlParam(EwsCommonConstants.SystemParam.SOCKET_USER_NAME); String roomId = socketIOClient.getHandshakeData().getSingleUrlParam(EwsCommonConstants.SystemParam.SOCKET_ROOM_ID); String appKey = socketIOClient.getHandshakeData().getSingleUrlParam(EwsCommonConstants.SystemParam.APP_KEY); if (StringUtils.isNotBlank(userName)) { logger.info("用户{}断开长连接通知, roomId: {}, NettySocketSessionId: {}, NettySocketRemoteAddress: {}", userName, roomId, socketIOClient.getSessionId().toString(), socketIOClient.getRemoteAddress().toString()); //移除客户端 String clientKey = StringUtils.concatStr(appKey, EwsCommonConstants.SystemParam.CON_SIGN, roomId); for (String key : clientMap.keySet()){ if (key.equals(clientKey)) { //移除该房间内的client clientMap.get(key).remove(socketIOClient.getSessionId()); } } } } /** * 监听事件 * @param socketIOClient 客户端 * @param ackRequest ack请求 * @param messageDto 消息主体 */ @OnEvent("ewsSocketMsg") public void ewsSocketMsg(SocketIOClient socketIOClient, AckRequest ackRequest, MessageDto messageDto){ String targetRoom = messageDto.getTargetRoom(); clientMap.forEach((key, value) ->{ //通过roomId获取 if (key.contains(targetRoom)) { logger.info("ewsSocketMsg: 收到客户{}的消息,发送给{}房间,消息内容是{}", messageDto.getSourceUserName(), messageDto.getTargetRoom(), messageDto.getMsgContent()); //判断房间内是否有client if (CollectionUtils.isNotEmpty(value)) { //获得该房间内所有广播对象发送事件 socketIOServer.getRoomOperations(key).sendEvent("ewsSocketMsg", messageDto); } } }); } /** * 服务端广播消息到所有客户端,自己除外 * @param socketIOClient 客户端 * @param ackRequest ack请求 * @param messageDto 消息主体 */ @OnEvent("ewsAllClientsMsg") public void ewsAllClientsMsg(SocketIOClient socketIOClient, AckRequest ackRequest, MessageDto messageDto){ logger.info("ewsAllClientsMsg:收到客户{}的消息,广播发送给其他客户,消息内容是{}", messageDto.getSourceUserName(), messageDto.getMsgContent()); try { socketIOServer.getBroadcastOperations().sendEvent("ewsAllClientsMsg", messageDto); } catch (Exception e) { e.printStackTrace(); } } /** * 去掉clientMap的无效appKey * 断开socketClient连接 * @param appKey 校验key */ public void invalidWebSocket(String appKey) { logger.info("去掉无效appKey"); clientMap.forEach((key, value) ->{ if (key.contains(appKey)) { value.forEach(e -> { //遍历该房间内的所有uuid,获取socketClient并断开连接 SocketIOClient socketIOClient = socketIOServer.getClient(e); socketIOClient.disconnect(); }); clientMap.remove(key); } }); logger.info("***无效appKey之后clientMap为: {}", JSON.toJSONString(clientMap)); } public Map getClientMap() { return clientMap; } public void setClientMap(Map clientMap) { this.clientMap = clientMap; } }

最后是socketIoServer启动类:

@Component @Order(1) public class SocketServer implements CommandLineRunner { /** * logger */ private static final Logger logger = LoggerFactory.getLogger(ServerRunner.class); /** * socketIOServer */ private final SocketIOServer socketIOServer; @Autowired public SocketServer(SocketIOServer socketIOServer) { this.socketIOServer = socketIOServer; } @Override public void run(String... args) { logger.info("---------- NettySocket通知服务开始启动 ----------"); socketIOServer.start(); logger.info("---------- NettySocket通知服务启动成功 ----------"); } }

以上为spring boot整合socketIo的步骤,作为WebSocket服务端使用。接下来介绍vue使用socket.io-client连接并监听服务端时间的实现: 引入client:import sio from ‘socket.io-client’ 在methods内添加connect()方法,方法内为具体实现如下:

connect:function(){ let opts = { query: 'userName=test&appKey=test&roomId=rabbit' }; // socketIo连接的服务器信息,就是我们后端配置的信息 let socket = sio.connect('http://localhost:8083?',opts); socket.on('connect', function () { console.log('websocket连接成功'); }); let that = this; socket.on('ewsSocketMsg', function (data) { console.log(data); thassageStatus = "消息状态:" + data.msgContent; }); socket.on('disconnect', function () { console.log('websocket已经下线'); }); /*socket.on('connect_error', (error) => { socket.close(); });*/ }

在mounted内执行connect方法:

mounted() { this.connect(); }

在启动了socketIo服务端的情况下,请求vue页面即可触发connect方法完成连接并监听,但是实际场景vue客户端会一直连接socketIo服务端,找了好久没定位到问题。最后发现很可能是因为socket.io的握手机制导致的。socket.io在进行握手的时候默认采用的是polling轮询机制进行的,当失败时会持续发送握手请求。

解决方案 在opts内添加transport参数的定义即可:

let opts = { query: 'userName=test&appKey=test&roomId=rabbit', transports:['websocket'] };

—————————————— JAVA面试知识点相关:JAVA面试知识点 SpringAOP原理使用相关:SpringAOP原理使用详解



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3